import 'dart:io';
import 'dart:convert';
import 'http_logger.dart';
import 'package:dio/io.dart';
import 'package:dio/dio.dart';
import 'result_analyzed.dart';
export 'result_analyzed.dart';
export 'package:dio/src/form_data.dart';

/// 请求方法枚举
enum HttpMethod { get, post }
/// 请求参数类型定义
typedef Request = Map<String, dynamic>;

// 客户端
final httpManager = HttpManager();


/// 多实例HTTP管理器
///
/// 该类允许创建多个HTTP管理器实例，每个实例可以有自己的基础URL、请求头和配置，
/// 适用于需要处理跨域请求的场景。
class HttpManager {

  final Dio _dio;

  /// 是否显示日志
  bool showLog;

  /// 基础URL
  String _baseUrl = "";

  /// 基础请求参数
  Request? _baseParam = {};

  /// 取消请求的令牌
  final CancelToken _cancelToken = CancelToken();

  /// 构造函数
  ///
  /// 创建一个新的HttpManager实例
  ///
  /// 参数:
  /// - `baseUrl`: 可选的基础URL
  /// - `headers`: 可选的请求头
  /// - `baseParam`: 可选的基础请求参数
  /// - `connectTimeout`: 连接超时时间，默认为60秒
  /// - `receiveTimeout`: 接收超时时间，默认为60秒
  /// - `showLog`: 是否显示日志，默认为false
  HttpManager({
    String baseUrl = "",
    Map<String, dynamic>? headers,
    Request? baseParam,
    Duration connectTimeout = const Duration(seconds: 60),
    Duration receiveTimeout = const Duration(seconds: 60),
    this.showLog = false,
  }) : _dio = Dio(BaseOptions(connectTimeout: connectTimeout, receiveTimeout: receiveTimeout)) {
    this.baseUrl = baseUrl;
    if (headers != null) {
      setHeaders(headers);
    }
    if (baseParam != null) {
      setBaseParam(baseParam);
    }
  }

  /// 获取Dio实例
  Dio get dio => _dio;

  /// 获取Dio选项
  BaseOptions get options => _dio.options;

  /// 设置基础URL
  ///
  /// 参数:
  /// - `newValue`: 新的基础URL
  set baseUrl(String newValue) {
    _baseUrl = newValue;
    _dio.options.baseUrl = _baseUrl;
  }

  /// 获取当前的基础URL
  String get baseUrl => _dio.options.baseUrl;


  /// 清除请求头
  void clearHeaders() => _dio.options.headers.clear();

  /// 设置请求头
  ///
  /// 这些头部信息将被添加到所有请求中。
  ///
  /// 参数:
  /// - `headers`: 类型为`Request`的请求头
  void setHeaders(Request headers) => _dio.options.headers.addAll(headers);



  /// 设置基础请求参数
  ///
  /// 这些参数将被添加到所有请求的查询参数中。
  ///
  /// 参数:
  /// - `param`: 类型为`Request`的请求参数
  void setBaseParam(Request? param) => _baseParam = param;

  /// 取消网络请求
  ///
  /// 取消指定的请求，如果没有指定请求，则取消所有请求。
  ///
  /// 参数:
  /// - `token`: 可选的取消令牌
  void cancel([CancelToken? token]) {
    if (token != null) token.cancel('cancel');
    _cancelToken.cancel('cancel');
  }

  /// 清除授权信息
  ///
  /// 将请求头中的授权信息设置为null，从而清除授权。
  void clearAuthorization() => setHeaders({"Authorization": null});

  /// 添加授权信息
  ///
  /// 将授权信息添加到请求头中。
  ///
  /// 参数:
  /// - `authorization`: 授权信息字符串
  set authorization(String authorization) => setHeaders({"Authorization": authorization});


  /// 设置代理
  ///
  /// 通过配置Dio的httpClientAdapter来设置代理服务器。
  /// 当代理服务器的主机和端口被指定时，所有通过Dio发送的HTTP请求
  /// 都将通过该代理服务器进行转发。
  ///
  /// 参数:
  /// - `host`: 代理服务器的主机地址，默认为空字符串
  /// - `port`: 代理服务器的端口号，默认为空字符串
  void setProxy({String? host = "", String? port = ""}) {
    _dio.httpClientAdapter = IOHttpClientAdapter(
      createHttpClient: () {
        final client = HttpClient();

        // 允许所有证书（包括不受信任的证书），用于开发环境
        client.badCertificateCallback = (cert, host, port) {
          return true; // 信任证书
        };

        // 如果host和port都不为空，设置代理
        if (host != null && host.isNotEmpty && port != null && port.isNotEmpty) {
          client.findProxy = (uri) {
            // 将请求代理至指定的host和port
            if (showLog) print("PROXY $host:$port");
            return "PROXY $host:$port"; // 设置代理地址
          };
          if (showLog) print("代理已设置: $host:$port");
        } else {
          // 清除代理设置
          client.findProxy = (uri) => 'DIRECT';
          if (showLog) print("代理已清除，使用直连");
        }

        return client;
      },
    );
  }

  /// 发送GET请求
  /// ====================== 发送 GET 请求 =======================
  ///
  /// 该方法将指定的URL和可选的查询参数传递给内部的`_request`方法，
  /// 以发送GET请求。请求完成后，返回服务器的响应。
  ///
  /// 参数:
  /// - `url`: 请求的URL地址
  /// - `params`: 可选的请求参数
  /// - `cancelToken`: 可选的取消令牌
  ///
  /// 返回:
  /// - 返回一个`Future<Response>`，表示服务器的响应结果
  Future<Response> get(String url, {Request? params, CancelToken? cancelToken}) async {
    return await _request(HttpMethod.get, url, params: params, cancelToken: cancelToken);
  }

  /// ====================== 发送 GET 请求 并解析 =======================
  /// 实例方法，用于发送 GET 请求，并对响应进行分析。
  ///
  /// 该方法会发送一个 GET 请求到指定的 URL，发送的请求参数是可选的。
  /// 请求完成后，响应会通过 `ResponseAnalyzed.analyzingAndCheckup` 方法进行分析，
  /// 并返回分析后的结果。
  ///
  /// 参数:
  /// - `url`: 请求的 URL 地址，类型为 `String`。
  /// - `params`: 可选的请求参数，类型为 `Request`。这些参数将作为请求体传递。
  /// - `cancelToken`: 可选的取消令牌，类型为 `CancelToken`，允许在必要时取消请求。
  ///
  /// 返回:
  /// - 返回一个 `Future<ResponseAnalyzed>`，表示分析后的响应结果。
  Future<ResponseAnalyzed> getAnalyzing(String url, {Request? params, CancelToken? cancelToken}) async {
    final response = await get(url, params: params, cancelToken: cancelToken);
    return await ResponseAnalyzed.analyzingAndCheckup(response);
  }

  /// ====================== 发送 POST 请求 =======================
  /// 发送POST请求
  ///
  /// 该方法将指定的URL和可选的请求参数传递给内部的`_request`方法，
  /// 以发送POST请求。请求完成后，返回服务器的响应。
  ///
  /// 参数:
  /// - `url`: 请求的URL地址
  /// - `params`: 可选的请求参数
  /// - `cancelToken`: 可选的取消令牌
  ///
  /// 返回:
  /// - 返回一个`Future<Response>`，表示服务器的响应结果
  Future<Response> post(String url, {Object? params, CancelToken? cancelToken}) async {
    return await _request(HttpMethod.post, url, params: params, cancelToken: cancelToken);
  }

  /// ====================== 发送 POST 请求 并解析 =======================
  /// 实例方法，用于发送 POST 请求，并对响应进行分析。
  ///
  /// 该方法会发送一个 POST 请求到指定的 URL，发送的请求参数是可选的。
  /// 请求完成后，响应会通过 `ResponseAnalyzed.analyzingAndCheckup` 方法进行分析，
  /// 并返回分析后的结果。
  ///
  /// 参数:
  /// - `url`: 请求的 URL 地址，类型为 `String`。
  /// - `params`: 可选的请求参数，类型为 `Request`。这些参数将作为请求体传递。
  /// - `cancelToken`: 可选的取消令牌，类型为 `CancelToken`，允许在必要时取消请求。
  ///
  /// 返回:
  /// - 返回一个 `Future<ResponseAnalyzed>`，表示分析后的响应结果。
  Future<ResponseAnalyzed> postAnalyzing(String url, {Object? params, CancelToken? cancelToken}) async {
    var response = await post(url, params: params, cancelToken: cancelToken);
    return await ResponseAnalyzed.analyzingAndCheckup(response);
  }

  /// ====================== 上传文件 并解析 =======================
  ///
  /// 这个方法通过Dio库发送POST请求，将FormData数据上传到服务器。
  /// 该方法还支持附加查询参数、监控上传进度，以及捕获和处理网络异常。
  ///
  /// 参数:
  /// - `url`: 目标上传的URL地址
  /// - `params`: 可选的请求参数，类型为Request，将作为查询参数附加到URL中
  /// - `formData`: 要上传的表单数据，类型为FormData，用于传递文件和其他表单字段
  /// - `onSendProgress`: 可选的上传进度回调函数
  /// - `cancelToken`: 可选的取消令牌
  ///
  /// 返回:
  /// - 返回一个Future<Response>，表示服务器的响应结果
  Future<Response> upload(String url, FormData formData, {ProgressCallback? onSendProgress, CancelToken? cancelToken}) async {
    Map<String, dynamic>? params = {};
    if (_baseParam != null) params.addAll(_baseParam!);
    formData.fields.addAll(params.entries.map((e) => MapEntry(e.key, e.value.toString())));
    final httpMethod = HttpMethod.post.name;
    final startTime = DateTime.now().toString();
    try {
      Response? response = await _dio.post(
        url,
        data: formData,
        queryParameters: params,
        onSendProgress: onSendProgress,
        options: Options(contentType: Headers.jsonContentType),
      );
      final endTime = DateTime.now().toString();
      _log(url, httpMethod, params, formData, startTime, endTime, response);
      return response;
    } on DioException catch (e) {
      final endTime = DateTime.now().toString();
      Response? response = _dioError(e);
      _log(url, httpMethod, params, formData, startTime, endTime, response);
      return response;
    }
  }

  /// ====================== 上传文件 并解析 =======================
  Future<ResponseAnalyzed> uploadAnalyzing(String url, FormData formData, {ProgressCallback? onSendProgress, CancelToken? cancelToken}) async {
    var response = await upload(url, formData, onSendProgress: onSendProgress, cancelToken: cancelToken);
    ResponseAnalyzed result = await ResponseAnalyzed.analyzingAndCheckup(response);
    return result;
  }

  /// 上传将流数据
  ///
  /// 这个方法通过Dio库发送POST请求，将Stream数据上传到服务器。
  /// 可以在上传过程中通过onSendProgress回调获取上传进度，并可以通过cancelToken取消上传操作。
  ///
  /// 注意：使用此方法时，需要确保Stream包含已知长度的数据，并在headers中设置Content-Length。
  ///
  /// 参数:
  /// - `url`: 目标上传的URL地址
  /// - `stream`: 要上传的数据流，通常用于上传大文件或流式数据
  /// - `onSendProgress`: 可选的上传进度回调函数
  /// - `cancelToken`: 可选的取消令牌
  ///
  /// 返回:
  /// - 返回一个Future<void>，表示上传操作的完成情况
  Future<void> uploadStream(String url, Stream stream, {ProgressCallback? onSendProgress, CancelToken? cancelToken}) async {
    await _dio.post(
      url,
      data: stream,
      options: Options(headers: {Headers.contentLengthHeader: stream.length}),
      onSendProgress: onSendProgress,
      cancelToken: cancelToken,
    );
  }


  /// 下载文件
  ///
  /// 该方法会将指定的URL指向的文件下载到本地，并将其保存到savePath路径下，
  /// 文件名为fileName。可以选择传递一个进度回调函数onReceiveProgress，
  /// 以便在下载过程中跟踪下载进度。
  ///
  /// 参数:
  /// - `url`: 要下载的文件的URL地址，如有中文，请进行编码处理
  /// - `savePath`: 文件保存的路径
  /// - `fileName`: 保存的文件名
  /// - `onReceiveProgress`: 可选的进度回调函数
  /// - `cancelToken`: 可选的取消令牌
  ///
  /// 返回:
  /// - 返回一个Future<Response>，表示下载操作的响应结果
  Future<Response> downloadFile(String url, String savePath, String fileName, {ProgressCallback? onReceiveProgress, CancelToken? cancelToken}) async {
    Map<String, dynamic>? params2 = {};
    Request? params;
    params2.addAll(params ?? {});
    if (_baseParam != null) params2.addAll(_baseParam!);
    HttpMethod method = HttpMethod.get;
    final httpMethod = method.toString().substring(11, method.toString().length);
    final startTime = DateTime.now().toString();
    try {
      // 使用Dio的download方法下载文件
      Response response = await _dio.download(
        url,
        "$savePath/$fileName",
        cancelToken: cancelToken,
        onReceiveProgress: (received, total) {
          if (onReceiveProgress != null) onReceiveProgress(received, total);
        },
      );
      // 记录下载日志
      final endTime = DateTime.now().toString();
      if (showLog) _log(url, httpMethod, params is Map ? params2 : params, "download", startTime, endTime, response, savePath: savePath);
      return response;
    } on DioException catch (e) {
      // 处理异常并记录日志
      final endTime = DateTime.now().toString();
      Response? response = _dioError(e);
      if (showLog) _log(url, httpMethod, params is Map ? params2 : params, "download", startTime, endTime, response, savePath: savePath);
      return response;
    }
  }

  /// 私有方法，用于发送HTTP请求
  ///
  /// 该方法根据指定的HTTP方法和URL发送请求，支持传递查询参数和取消令牌。
  /// 请求过程会记录开始时间和结束时间，并在请求成功或失败后记录日志信息。
  ///
  /// 参数:
  /// - `method`: 请求的HTTP方法
  /// - `url`: 请求的URL地址
  /// - `params`: 可选的请求参数
  /// - `cancelToken`: 可选的取消令牌
  ///
  /// 返回:
  /// - 返回一个Future<Response>，表示服务器的响应结果
  Future<Response> _request(HttpMethod method, String url, {Object? params, CancelToken? cancelToken}) async {
    // 合并传入的请求参数和基础参数
    Map<String, dynamic>? params2 = {};
    if (params is Map) params2.addAll(params as Map<String, dynamic>? ?? {});
    if (_baseParam != null) params2.addAll(_baseParam!);
    // 提取HTTP方法的名称
    final httpMethod = method.toString().substring(11, method.toString().length);
    final startTime = DateTime.now().toString();
    try {
      // 发送请求并获取响应
      params = params ?? params2;
      Response response = await _sendRequest(method, url, params is Map ? params2 : params, cancelToken);
      final endTime = DateTime.now().toString();
      // 记录请求日志
      _log(url, httpMethod, params is Map ? params2 : params, null, startTime, endTime, response);
      return response;
    } on DioException catch (e) {
      // 处理异常并记录日志
      final endTime = DateTime.now().toString();
      Response? response = _dioError(e);
      _log(url, httpMethod, params2, null, startTime, endTime, response);
      return response;
    }
  }

  /// 发送HTTP请求
  ///
  /// 根据指定的请求方法（GET或POST）向指定的URL发送请求。
  ///
  /// 参数:
  /// - `method`: 请求的方法类型
  /// - `url`: 请求的URL地址
  /// - `params`: 可选的请求参数
  /// - `cancelToken`: 可选的取消令牌
  ///
  /// 返回:
  /// - 返回一个Future<Response>，表示服务器的响应结果
  Future<Response> _sendRequest(HttpMethod method, String url, Object? params, CancelToken? cancelToken) async {
    final data = jsonEncode(params);
    final options = Options(contentType: Headers.jsonContentType);
    try {
      switch (method) {
        case HttpMethod.get:
          // 发送 GET 请求，参数作为查询参数附加到 URL
          return await dio.get(url, data: data, queryParameters: params as Map<String, dynamic>, options: options, cancelToken: cancelToken);
        case HttpMethod.post:
          // 发送 POST 请求，参数经过 JSON 编码后作为请求体数据
          return await dio.post(url, data: data, options: options, cancelToken: cancelToken);
        }
    } on DioException catch (e) {
      // 处理 Dio 异常并返回处理后的响应
      return _dioError(e);
    }
  }

  /// 处理错误信息
  Response _dioError(DioException error) {
    Response? response = error.response;
    if (response != null) {
      if (response.data is Map) {
        Map data = error.response?.data;
        if (data.containsKey("code")) {
          response.data["code"] = data["code"];
        }
        if (data.containsKey("message")) {
          response.data["data"] = data["message"];
        }
      } else if (response.statusCode != null || response.statusMessage != null || response.data == null) {
        response.data = {"code": response.statusCode, "message": response.statusMessage, "data": response.data};
      }
    } else {
      response = Response(requestOptions: RequestOptions(data: error.requestOptions), data: {});
      switch (error.type) {
        case DioExceptionType.connectionTimeout:
          response.data = {"code": 500, "message": '连接超时', "data": ""};
        case DioExceptionType.sendTimeout:
          response.data = {"code": 400, "message": '发送超时', "data": ""};
        case DioExceptionType.receiveTimeout:
          response.data = {"code": 500, "message": '响应超时', "data": ""};
        case DioExceptionType.badResponse:
          response.data = {"code": 404, "message": '请求地址不存在', "data": ""};
        case DioExceptionType.cancel:
          response.data = {"code": 400, "message": '请求取消', "data": ""};
        case DioExceptionType.unknown:
          response.data = {"code": 400, "message": '未知错误,请检查网络或服务器', "data": ""};
        default:
          response.data = {"code": 400, "message": '未知错误,请检查网络或服务器', "data": ""};
      }
    }
    return response;
  }

  _log(url, httpMethod, params2, formData, startTime, endTime, response, {String? savePath}) async {
    if (showLog == true) {
      final baseUrl = _dio.options.baseUrl;
      if (formData == null) {
        HttpLogger.debug(
          operationType: "请求",
          url: "$baseUrl$url",
          httpMethod: httpMethod,
          header: options.headers,
          startTime: startTime,
          endTime: endTime,
          requestParams: params2,
          responseData: response,
        );
      }else if (formData == "download") {
        HttpLogger.debug(
            operationType: "下载",
            url: "$baseUrl$url",
            httpMethod: httpMethod,
            header: options.headers,
            startTime: startTime,
            endTime: endTime,
            requestParams: params2,
            responseData: response,
            savePath: savePath
        );

      }else {
        HttpLogger.debug(
          operationType: "上传",
          url: "$baseUrl$url",
          httpMethod: httpMethod,
          header: options.headers,
          startTime: startTime,
          endTime: endTime,
          formData: formData,
          responseData: response,
        );
      }
    }
  }

} 